package com.sojson.util.mq.rabbit.code.interfaces.impl;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import com.rabbitmq.client.AMQP.BasicProperties.Builder;
import com.rabbitmq.client.AMQP.Queue.BindOk;
import com.rabbitmq.client.Channel;
import com.sojson.util.StringUtil;
import com.sojson.util.mq.rabbit.code.interfaces.IRabbitCodeConsumeUtil;
import com.sojson.util.mq.rabbit.code.interfaces.IRabbitCodeExchangeUtil;
import com.sojson.util.mq.rabbit.code.interfaces.IRabbitCodeMsgUtil;
import com.sojson.util.mq.rabbit.code.interfaces.IRabbitCodeQueueUtil;
import com.sojson.util.mq.rabbit.enums.ERabbitExchange;

/**
 * Rabbit-Headers(用请求头(Map中的参数)指定匹配)工具类</BR>
 * 功能和{@link com.sojson.util.mq.rabbit.code.interfaces.impl.RabbitCodeDirectUtilImpl}一样</BR>
 * 把原来的参数匹配换成了请求头(Map中的参数)匹配</BR>
 * 请求头中必须包含x-match参数,值为all或any</BR>
 * all:表示需要全部匹配</BR>
 * any:表示只要匹配到headers表中的任何一个键值即可</BR>
 * 
 * @author liu
 * @date 2022-06-16
 */
public class RabbitCodeHeadersUtilImpl
    implements IRabbitCodeExchangeUtil, IRabbitCodeQueueUtil, IRabbitCodeMsgUtil, IRabbitCodeConsumeUtil {

    private static RabbitCodeHeadersUtilImpl instances;

    /** 默认参数匹配类型的Key */
    private static final String X_MATCH_KEY = "x-match";
    /** 匹配类型(并且关系) */
    private static final String X_MATCH_VALUE_ALL = "all";
    /** 匹配类型(或者关系) */
    private static final String X_MATCH_VALUE_OR = "any";

    /**
     * 将交换机和队列绑定
     * 
     * @param channel
     * @param exchange      交换机名称
     * @param queue         队列名称
     * @param arguments     其他参数(默认为null)
     * @return
     * @throws IOException
     */
    public BindOk queueBind(Channel channel, String exchange, String queue, Map<String, Object> arguments)
        throws IOException {
        return channel.queueBind(getQueueName(queue), getExchangeName(exchange), StringUtil.EMPTY,
            arguments);
    }

    /**
     * 开始生产消息
     * 
     * @param channel
     * @param exchange      交换机的名称
     * @param body          内容
     * @param headerParam   请求头参数
     * @return
     * @throws IOException
     */
    public void basicPublish(Channel channel, String exchange, String body, Map<String, Object> headerParam)
        throws IOException {
        basicPublish(channel, exchange, false, body, headerParam);
    }

    /**
     * 开始生产消息
     * 
     * @param channel
     * @param exchange      交换机的名称
     * @param mandatory     是否开启消息故障检测模式
     *                      它只会让RabbitMq向你通知失败,而不会通知成功
     *                      设为true的时候,使用前要先调用channel.addReturnListener(new ReturnCallback(){})
     *                      channel.addReturnListener(new ReturnListener(){})为异步返回
     *                      如果为true,消息不能路由到指定的队列时,会触发channel.BasicReturn事件
     *                      如果为false,则broker会直接将消息丢弃
     * @param body          内容
     * @param headerParam   请求头参数
     * @return
     * @throws IOException
     */
    public void basicPublish(Channel channel, String exchange, boolean mandatory, String body,
        Map<String, Object> headerParam) throws IOException {
        basicPublish(channel, exchange, mandatory, false, getBasicProperties(), body, headerParam);
    }

    /**
     * 开始生产消息
     * 
     * @param channel
     * @param exchange      交换机的名称
     * @param mandatory     是否开启消息故障检测模式
     *                      它只会让RabbitMq向你通知失败,而不会通知成功
     *                      设为true的时候,使用前要先调用channel.addReturnListener(new ReturnCallback(){})
     *                      channel.addReturnListener(new ReturnListener(){})为异步返回
     *                      如果为true,消息不能路由到指定的队列时,会触发channel.BasicReturn事件
     *                      如果为false,则broker会直接将消息丢弃
     * @param immediate     RabbitMQ 3.0版本开始去掉了对immediate参数的支持
     *                      原因是因为immediate参数会影响镜像队列的性能,增加了代码的复杂度,建议采用TTL和DLX的方法替代
     *                      当immediate参数设置为true时,如果交换器再将消息路由到一个队列中,否则将消息返回给生产者
     *                      immediate参数告诉服务器,如果该消息关联的队列上有消费者,则立刻投递
     *                      如果所有匹配的队列上都没有消费者,则直接将消息返还给生产者,不用将消息存于的队列而等待消费者
     * @param props         其它的一些属性(内容格式,类型,请求头等)
     * @param body          内容
     * @param headerParam   请求头参数
     * @return
     * @throws IOException
     */
    public void basicPublish(Channel channel, String exchange, boolean mandatory, boolean immediate, Builder props,
        String body, Map<String, Object> headerParam) throws IOException {
        if (isBlankObject(props)) {
            props = getBasicProperties();
        }
        Map<String, Object> headers = props.build().getHeaders();
        if (isBlankObject(headers)) {
            headers = new HashMap<>(10);
        }
        if (isNotBlankObject(headerParam)) {
            headers.putAll(headerParam);
        }
        props = props.headers(headers);
        channel.basicPublish(getExchangeName(exchange), StringUtil.EMPTY, mandatory, immediate,
            props.build(), body.getBytes());
    }

    /** 获取 交换机和队列绑定时的属性(多参数匹配的时候是并且关系) */
    public Map<String, Object> getAllParam() {
        Map<String, Object> map = new HashMap<>(10);
        map.put(X_MATCH_KEY, X_MATCH_VALUE_ALL);
        return map;
    }

    /** 获取 交换机和队列绑定时的属性(多参数匹配的时候是或者关系) */
    public Map<String, Object> getOrParam() {
        Map<String, Object> map = new HashMap<>(10);
        map.put(X_MATCH_KEY, X_MATCH_VALUE_OR);
        return map;
    }

    /**
     * 获取交换机类型
     * 
     * @return
     */
    @Override
    public String getExchangeType() {
        return ERabbitExchange.HEADERS.getCode();
    }

    /**
     * 判断一个对象是否是空(空对象)
     * 
     * @param obj
     * @return
     */
    private static boolean isBlankObject(Object obj) {
        return StringUtil.isBlankObject(obj);
    }

    /**
     * 判断一个对象是否不是空(空对象)
     * 
     * @param obj
     * @return
     */
    private static boolean isNotBlankObject(Object obj) {
        return StringUtil.isNotBlankObject(obj);
    }

    public static RabbitCodeHeadersUtilImpl getInstances() {
        if (isBlankObject(instances)) {
            instances = new RabbitCodeHeadersUtilImpl();
        }
        return instances;
    }

}